RBAC (Role-Based Access Control) 基於角色的存取控制 (RBAC) 是一種控制資源存取的方法。使用者透過角色與作為安全目標的系統資源相關聯,並且使用者和系統資源之間的關係用於確定是否允許存取。

我們可以從公司制度理解這個概念:身為一個外人 (User),在公司是沒有任何權利,寸步難行。一旦你進入公司 (Binding) 成為管理人員職位 (Role),你便獲得了這個職位的權利,比如員工資料的查詢、修改權限等等。
RBAC 通過提供精細的訪問控制、簡化權限管理、提高安全性、支持多租戶環境、增強合規性和審計能力、提供靈活性和可擴展性,以及減少錯誤和提高運營效率,為系統的安全性和管理帶來了顯著的好處。

在 Kubernetes 裡也有 RBAC 方法,RBAC API 聲明了四種 Kubernetes 對象:
RBAC 的 Role 或 ClusterRole 中包含一組代表相關權限的規則。 這些權限是純粹累加的(不存在拒絕某操作的規則)。
Role 與 ClusterRole 很相似,但 Role 只能用來定義特定 namespace 內的資源操作,而 ClusterRole 則可以定義整個集群範圍內的資源操作。簡單來說:
查詢資源做用於 namespace 還是 Cluster,可以用以下指令查詢:
# 做用於 Namespace-scoped 的資源
kubectl api-resources --namespaced
# 做用於 Cluster-scoped 的資源
kubectl api-resources --namespaced=false
Role 示例
這是一個位於 default namespace 的 Role 的示例,可用來授予對 Pod 的讀存取權:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" 標明 core API 組
resources: ["pods"]
verbs: ["get", "watch", "list"]
ClusterRole 示例
這是一個 ClusterRole 的示例,可用來授予對 Secret 的讀存取權:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# "namespace" 被忽略,因為 ClusterRoles 不受 namespace 限制
name: secret-reader
rules:
- apiGroups: [""]
# 在 HTTP 層面,用來訪問 Secret 資源的名稱為 "secrets"
resources: ["secrets"]
verbs: ["get", "watch", "list"]
不過上面我們也提到過,ClusterRole 創建的讀存權限,實際上也可以授予給特定 namespace,這取決於我們使用 RoleBinding 還是 ClusterRoleBinding。
角色绑定(Role Binding)是將角色中定義的權限賦予一個或者一組使用者。 它包含若干主體(Subject)(使用者、組或服務帳戶)的列表和對這些主體所獲得的角色的引用。 RoleBinding 在指定的 namespace 中執行授權,而 ClusterRoleBinding 在叢集範圍執行授權。
一個 RoleBinding 可以引用同一的 namespace 中的任何 Role。 或者,一個 RoleBinding 可以引用某 ClusterRole 並將該 ClusterRole 繫結到 RoleBinding 所在的 namespace 。 如果你希望將某 ClusterRole 繫結到叢集中所有 namespace,你要使用 ClusterRoleBinding。
用表格將 RoleBinding 和 ClusterRoleBinding 的內容整理如下:
| 特性 | RoleBinding | ClusterRoleBinding |
|---|---|---|
| 角色定義的權限賦予 | 將角色中定義的權限賦予一個或者一組使用者 | 同左 |
| 主體(Subject)列表 | 包含使用者、組或服務帳戶 | 同左 |
| 授權範圍 | 在指定的 namespace 中執行授權 | 在叢集範圍內執行授權 |
| 引用 Role | 可以引用同一 namespace 中的任何 Role | 不適用 |
| 引用 ClusterRole | 可以引用 ClusterRole 並繫結到 RoleBinding 所在的 namespace | 繫結 ClusterRole 到叢集中所有 namespace |
RoleBinding 示例
下面的例子中的 RoleBinding 將 pod-reader Role 授予在 default namespace 中的使用者 jane。 這樣,使用者 jane 就具有了讀取 default namespace 中所有 Pod 的權限。
apiVersion: rbac.authorization.k8s.io/v1
# 此角色繫結允許 "jane" 讀取 "default" namespace 中的 Pod
# 你需要在該 namespace 中有一個名為 “pod-reader” 的 Role
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
# 你可以指定不止一個“subject(主體)”
- kind: User
name: jane # "name" 是區分大小寫的
apiGroup: rbac.authorization.k8s.io
roleRef:
# "roleRef" 指定與某 Role 或 ClusterRole 的繫結關係
kind: Role # 此欄位必須是 Role 或 ClusterRole
name: pod-reader # 此欄位必須與你要繫結的 Role 或 ClusterRole 的名稱匹配
apiGroup: rbac.authorization.k8s.io
RoleBinding 也可以引用 ClusterRole,以將對應 ClusterRole 中定義的存取權授予 RoleBinding 所在 namespace 的資源。這種引用使得你可以跨整個叢集定義一組通用的角色, 之後在多個 namespace 中復用。
例如,儘管下面的 RoleBinding 引用的是一個 ClusterRole,dave 只能訪問 development namespace 中的 Secret 對象,因為 RoleBinding 所在的 namespace 是 development。
apiVersion: rbac.authorization.k8s.io/v1
# 此角色繫結使得使用者 "dave" 能夠讀取 "development" namespace 中的 Secret
# 你需要一個名為 "secret-reader" 的 ClusterRole
kind: RoleBinding
metadata:
name: read-secrets
# RoleBinding 的 namespace 決定了存取權的授予範圍。
# 這裡隱含授權僅在 "development" namespace 內的存取權。
namespace: development
subjects:
- kind: User
name: dave # 'name' 是區分大小寫的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
ClusterRoleBinding 示例
要跨整個叢集完成存取權的授予,你可以使用一個 ClusterRoleBinding。 下面的 ClusterRoleBinding 允許 "manager" 組內的所有使用者訪問任何 namespace 中的 Secret。
apiVersion: rbac.authorization.k8s.io/v1
# 此叢集角色繫結允許 “manager” 組中的任何人訪問任何 namespace 中的 Secret 資源
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager # 'name' 是區分大小寫的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
建立了繫結之後,你不能再修改繫結對象所引用的 Role 或 ClusterRole,也就是 roleRef 欄位。如果你想要改變現有繫結對象中 roleRef 欄位的內容,必須刪除重新建立繫結對象。
大多數資源由代表其名字的字串表示,例如 pods,就像它們出現在相關 API endpoint 的 URL 中一樣。然而,有一些 Kubernetes API 還包含了"子資源",比如 pod 的 logs。在 Kubernetes 中,pod logs endpoint 的 URL 格式為:
GET /api/v1/namespaces/{namespace}/pods/{name}/log
在這種情況下,"pods" 是 namespace 資源,而 "log" 是 pods 的子資源。為了在 RBAC 中表示出這一點,我們需要使用斜線來劃分資源 與子資源。如果需要角色繫結主體讀取 pods 以及 pod log,需要這樣定義角色:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
另外還可以通過 resourceNames 欄位針對特定資源賦予權限。例如,如果需要限定一個角色繫結主體只能 "get" 或者 "update" 一個 configmap 時,您可以定義以下角色:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: configmap-updater
rules:
- apiGroups: [""]
resources: ["configmap"]
resourceNames: ["my-configmap"]
verbs: ["update", "get"]
值得注意的是,如果設定了 resourceNames,則請求所使用的動詞不能是 list、watch、create 或者 deletecollection。 由於資源名不會出現在 create、list、watch 和 deletecollection 等 API 請求的 URL 中。
kubectl create serviceaccount demosa
apiVersion: v1
kind: Pod
metadata:
name: client
spec:
serviceAccount: demosa
containers:
- name: client
image: nginx
kubectl describe pod client
結果如下
Name: client
Namespace: default
Priority: 0
Service Account: demosa
[...]
Containers:
client:
[...]
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-pbz28 (ro)
[...]
kubectl exec client -- ls -l /var/run/secrets/kubernetes.io/serviceaccount
結果如下
total 0
lrwxrwxrwx 1 root root 13 Aug 7 10:18 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Aug 7 10:18 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Aug 7 10:18 token -> ..data/token
ca.crt 是一個 CA 憑證,用於驗證 Kubernetes API 伺服器的 SSL/TLS 憑證。Pod 在與 Kubernetes API 進行安全通信時,可以使用這個憑證來確保通信的安全性和完整性。
namespace 文件包含了 Pod 所在的 Kubernetes namespace 的名稱。該名稱用於在 API 請求中識別 Pod 的作用範圍。
token 是一個 Bearer Token (JWT 格式),用於 Pod 與 Kubernetes API 伺服器進行身份驗證。當 Pod 需要與 Kubernetes API 互動(例如讀取 ConfigMap、Secrets,或查詢叢集狀態)時,這個 Token 會被用來進行身份認證。
使用 JSON Web Tokens 解碼 token 內容,結果如下:
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1754561936,
"iat": 1723025936,
"iss": "https://kubernetes.default.svc.cluster.local",
"jti": "32b625d5-40f1-4d2a-ba0f-88691886e6a9",
"kubernetes.io": {
"namespace": "default",
"node": {
"name": "wslkind-worker2",
"uid": "012ff8ef-8a88-4289-8fc4-0751ea78030f"
},
"pod": {
"name": "client",
"uid": "15396187-91f6-4ccc-9634-a91d4daed87e"
},
"serviceaccount": {
"name": "demosa",
"uid": "4f4bbe18-f38f-42bc-b0ca-aa379fdc4a45"
},
"warnafter": 1723029543
},
"nbf": 1723025936,
"sub": "system:serviceaccount:default:demosa"
}
kubectl exec -it client -- sh
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
curl --cacert $CACERT -X GET https://kubernetes.default.svc.cluster.local/api
結果如下
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/api\"",
"reason": "Forbidden",
"details": {},
"code": 403
}
我們已經成功連線到 api service,不過我們沒通過驗證。
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" -X GET https://kubernetes.default.svc.cluster.local/api
結果如下
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "172.18.0.3:6443"
}
]
}
我們已經成功通過驗證,取得回應。
雖然我們已經可以訪問 K8s api service,但我們沒有任何資源的操作權限,因為我們沒有綁定任何角色,這裡我們可以檢驗 Service Account 是否有權限對特定資源進行特定操作:
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" -X GET https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods
結果如下
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "pods is forbidden: User \"system:serviceaccount:default:demosa\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
"reason": "Forbidden",
"details": {
"kind": "pods"
},
"code": 403
}
另一種驗證方法:透過 kubectl auth can-i 指令:
kubectl auth can-i list pods --as=system:serviceaccount:default:demosa
---
no
現在我們要將 demosa 跟 Role 綁定,使 demosa 有權限完成對特定資源的操作。
# 建立角色 demorole,授予可以 get, list 資源 pods 的權限
kubectl create role demorole --verb=get,list --resource=pods
# 建立角色關聯 demorolebinding,將角色 demorole 與服務帳號 demosa 關聯
kubectl create rolebinding demorolebinding --role=demorole --serviceaccount=default:demosa
demosa 的操作權限kubectl auth can-i list pods --as=system:serviceaccount:default:demosa
---
yes
我們試著從 Pod 內部直接訪問 api service。
kubectl exec -it client -- sh
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" -X GET https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods
結果如下
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "737483"
},
"items": [...]
}
kubectl delete rolebinding demorolebinding
kubectl delete role demorole
kubectl delete serviceaccount demosa
我們會在這一章節實作,如何從頭建立一個新的 Normal User,然後透過 RBAC 賦予使用者合適的權限管理叢集。
我們需要為 Client Certificates 作為驗證手段,因此請在客戶端事先安裝 openssl。
使用者私鑰
openssl genrsa -out demouser.key 2048
使用者私鑰產生憑證簽署請求 (CSR)。openssl req -new -key demouser.key -out demouser.csr -subj "/CN=demouser/O=rd"
K8s 會查看 X.509 證書中的主體資訊 (Subject),將 CN (Common Name) 當作 UserName,O (Organization) 當作 Group。
接下來我們要使用 K8s 的 CA 憑證來簽署 CSR,有兩個做法。
cat demouser.csr | base64 | tr -d "\n"
組態文件: demouser-csr.yaml
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: demouser
spec:
groups:
- system:authenticated
# 將 Base64 加密過的 CSR 貼在這裡
request: <CSR>
signerName: kubernetes.io/kube-apiserver-client
usages:
- client auth
kubectl apply -f demouser-csr.yaml
CertificateSigningRequest 詳細內容kubectl describe certificatesigningrequests demouser
---
Name: demouser
Labels: <none>
Annotations: <ignore>
CreationTimestamp: Thu, 08 Aug 2024 02:05:29 +0800
Requesting User: kubernetes-admin
Signer: kubernetes.io/kube-apiserver-client
Status: Pending
Subject:
Common Name: demouser
Serial Number:
Events: <none>
可以看到登記的使用者是 CN 定義的 demouser,建立請求者是 kubernetes-admin 這個我們正在使用的預設 User。
目前這個請求狀態還在 Pending 中,接下來要使用 kubectl 命令批准這個請求。
CertificateSigningRequest
kubectl certificate approve demouser
kubectl describe certificatesigningrequests demouser
---
Name: demouser
Labels: <none>
Annotations: <ignore>
CreationTimestamp: Thu, 08 Aug 2024 02:05:29 +0800
Requesting User: kubernetes-admin
Signer: kubernetes.io/kube-apiserver-client
Status: Approved,Issued
Subject:
Common Name: demouser
Serial Number:
Events: <none>
這樣我們就算完成了簽署。接下來我們要把使用者公鑰提取出來。
使用者公鑰,並將其用 Base64 解碼,輸出為 demouser.crt 檔案kubectl get certificatesigningrequests demouser -o jsonpath='{.status.certificate}' | base64 --decode > demouser.crt
使用者公鑰的內容openssl x509 -noout -in demouser.crt -text
結果如下
Certificate:
Data:
[...]
Issuer: CN = kubernetes
Validity
Not Before: Aug 7 21:14:41 2024 GMT
Not After : Aug 7 21:14:41 2025 GMT
Subject: CN = demouser
[...]
不透過叢集簽署,我們需要取得叢集的 CA 私鑰和證書。
一般來說我們需要進入叢集任一節點中取得位於路徑 /etc/kubernetes/pki 下的 ca.crt, ca.key 檔案。但我們的叢集節點等於 docker 容器,可以簡單的透過 docker cp 指令下載檔案 。
ca.crt 和 ca.key 檔案docker cp wslkind-control-plane:/etc/kubernetes/pki/ca.key ./ca.key
docker cp wslkind-control-plane:/etc/kubernetes/pki/ca.crt ./ca.crt
現在我們在本地的目錄應該會有以下 4 個檔案: ca.key, ca.crt, demouser.key, demouser.csr
demouser.crt
openssl x509 -req -in demouser.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out demouser.crt -days 365
有了使用者證書後,接下來要在 kubeconfig 建立對應的 User 和 Context,才能在 kubectl 使用。
kubectl config set-credentials demouser \
--client-certificate=demouser.crt \
--client-key=demouser.key \
--embed-certs=true
kubectl config get-users
---
NAME
demouser
kind-wslkind
kubectl config view --minify -o jsonpath="{.clusters[0].name}"
---
kind-wslkind
kubectl config set-context demo --user=demouser --cluster=kind-wslkind
kubectl config get-contexts
---
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
demo kind-wslkind demouser
* kind-wslkind kind-wslkind kind-wslkind
儘管我們成功的建立 User 和 Context,但我們並沒有賦予任何權限給 demouser,因此我們只能訪問叢集,而無法進行任何資源。我們可以測試一下:
kubectl config use-context demo
kubectl get node
---
Error from server (Forbidden): nodes is forbidden: User "demouser" cannot list resource "nodes" in API group "" at the cluster scope
現在我們要為 demouser 加上權限,使它有權限完成對資源的操作
kubectl config use-context kind-wslkind
# 建立允許只讀 pod 權限的叢集角色,用 rolebinding 將角色與使用者綁定,並指定 namespace kube-system
kubectl create clusterrole pod-viewer --verb=get,list --resource=pods
kubectl create rolebinding demouser-pod-viewer-binding --clusterrole=pod-viewer --user=demouser --namespace=kube-system
---
# 建立允許只讀 node 權限的叢集角色,用 clusterrolebinding 將角色與使用者綁定
kubectl create clusterrole node-viewer --verb=get,list --resource=nodes
kubectl create clusterrolebinding demouser-node-viewer-binding --clusterrole=node-viewer --user=demouser
---
# 建立允許只讀 clusterroles 權限的叢集角色,用 clusterrolebinding 將角色與 Group rd 綁定
kubectl create clusterrole node-viewer --verb=get,list --resource=clusterroles
kubectl create clusterrolebinding demouser-node-viewer-binding --clusterrole=node-viewer --group=rd
kubectl auth can-i get pods -n default --as=demouser
no
---
kubectl auth can-i get pods -n kube-system --as=demouser
yes
---
kubectl auth can-i get nodes --all-namespaces --as=demouser
yes
---
kubectl auth can-i get clusterroles --all-namespaces --as=demouser
no
---
kubectl auth can-i get clusterroles --all-namespaces --as=whatever --as-group=rd
yes
kubectl config use-context kind-wslkind
kubectl delete clusterrolebinding demouser-node-viewer-binding
kubectl delete rolebinding demouser-pod-viewer-binding -n kube-system
kubectl delete clusterrole pod-viewer node-viewer
kubectl config unset contexts.demo
kubectl config unset users.demouser
到今天為止,本系列教學的本篇算是告一段落。感謝各位讀者一路陪伴完成這系列的 Kubernetes 教學。希望大家在學習後,能夠勇敢地嘗試使用這些工具,解決實際問題,並將所學延伸到更廣的應用場景中。
Kubernetes 是一個充滿彈性和可能性的工具,只有透過實踐和探索,才能真正掌握它的力量。如果你在實作過程中有遇到挑戰,請不要害怕去尋找解決方案,這也是成長的重要一環。
未來我有新的心得或發現,也會以番外篇的形式,在部落格持續更新,與大家分享。希望我們能一同進步、探索更多可能性!
歡迎大家來我的部落格看看其他文章:Vincent's Blog